package com.hky.algorithm.webSocket;

import com.alibaba.fastjson.JSON;
import com.hky.algorithm.mahjongGame.*;
import com.hky.algorithm.mahjongGame.constent.GangType;
import com.hky.algorithm.mahjongGame.constent.Protocol;
import com.hky.algorithm.realize.Type;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * webSocket服务类，每次客户端连接的时候都会创建一个新的类。
 * 注意：不是单例的
 */
@Component
@Slf4j
@ServerEndpoint("/myWebSocket/{name}/{roomId}") // 服务端点，地址是“/myWebSocket”
public class MyWebSocket {

    // 连接的客户端
    private static final ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();

    // 所有房间 key=roomId
    private static final ConcurrentHashMap<Integer, GameService> roomMap = new ConcurrentHashMap<>();

    /**
     * 自摸时处理对应pass函数
     */
    private static final int SELF_HU = -2;

    // 下一个房间号
    private static final AtomicInteger nextRoomId = new AtomicInteger(1);

    // 玩家客户端
    private Session session;

    // 玩家实体类
    private GamePlayer curPlayer;

    // 房间号
    private Integer roomId;

    // 游戏服务
    private GameService gameService;

    /**
     * 注解 @OnOpen 表示方法是一个监听连接成功的回调方法
     *
     * @param session webSocket 的session
     * @param name    参数
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("name") String name, @PathParam("roomId") Integer id) {
        this.session = session;
        this.roomId = id;
        sessionMap.put(session.getId(), session);
        log.info("webSocket已连接，连接客户端数量：" + sessionMap.size());
        log.info("sessionID = " + session.getId());
        log.info("name = " + name);

        if (id.equals(0)) {
            // 创建一个房间
            creatRoom();
        }
        // 加入房间
        boolean success = joinRoom(new GamePlayer(session.getId(), name));
        if (!success) return;

        // 发送 roomeID sessionID到登陆的客户端
        sendIDs(session);
        // 发送所有玩家信息到所有客户端
        sendAllPlayers();
    }

    /**
     * 发送所有玩家信息到所有客户端
     */
    private void sendAllPlayers() {
        List<GamePlayer> players = gameService.gameData.players;
        String sendPlayers = JSON.toJSONString(new ProtocolItem(Protocol.SEND_PLAYERS, players));
        for (GamePlayer player : players) {
            sendMsg(sessionMap.get(player.sessionId), sendPlayers);
        }
    }

    /**
     * 发送 roomeID sessionID到客户端!
     *
     * @param session 登录的客户端
     */
    private void sendIDs(Session session) {
        sendMsg(session, new ProtocolItem[]{new ProtocolItem(Protocol.SEND_ROOM_ID, this.roomId), new ProtocolItem(Protocol.SEND_SESSION_ID, this.curPlayer)});
    }


    /**
     * player 加入房间
     */
    private boolean joinRoom(GamePlayer player) {
        this.gameService = roomMap.get(roomId);
        if (gameService == null) {
            errorAlert("房间不存在！！！");
            return false;
        }
        synchronized (gameService) {
            if (gameService.alreadyStart || gameService.gameData.players.size() == 4) {
                errorAlert("房间人数已满！！！");
                return false;
            }
        }
        this.curPlayer = player;
        player.order = gameService.gameData.players.size();
        gameService.addGamePlayer(player);

        return true;
    }

    /**
     * 出现错误，需要弹出提示框
     *
     * @param msg 提示消息
     */
    private void errorAlert(String msg) {
        sendMsg(session, new ProtocolItem(Protocol.ERROR_ALERT, msg));
    }

    /**
     * 创建一个房间
     */
    private void creatRoom() {
        this.roomId = nextRoomId.getAndIncrement();
        this.gameService = new GameService(roomId);
        roomMap.put(roomId, gameService);
    }

    /**
     * 注解 @OnClose 表示方法是一个监听连接关闭的回调方法
     */
    @OnClose
    public void onClose() {
        sessionMap.remove(session.getId());
        log.info("连接关闭");
    }

    /**
     * 注解 @OnMessage 表示方法是一个监听接收到消息的回调方法
     */
    @OnMessage
    public void onMessage(String msg) {
        log.info("接收消息:" + msg + "，sessionId = " + session.getId() + ", name = " + curPlayer.name);
        ProtocolItem instruct = JSON.parseObject(msg, ProtocolItem.class);

        switch (instruct.protocol) {
            case BEGIN_GAME:
                beginGame();
                break;
            case OUT_CARD:
                playerOutCard(JSON.parseObject(instruct.dataString, Card.class));
                break;
            case RECEIVE_LACK_TYPE:
                receiveLackType(Type.valueOf(instruct.dataString));
                break;
            case HU:
                hu(JSON.parseObject(instruct.dataString, Object[].class));
                break;
            case PASS:
                pass(JSON.parseObject(instruct.dataString, Object[].class));
                break;
            case PLAYER_PENG_OR_GANG:
                playerPengOrGang(JSON.parseObject(instruct.dataString, GangItem.class));
                break;
            case TEST:
                break;
            default:
                log.error("客户端指令解析错误，msg = {}", msg);
        }
    }

    /**
     * 玩家选择碰或者杠
     */
    private void playerPengOrGang(GangItem gangItem) {
        // 更新算法层数据
        gameService.pengOrGang(curPlayer, gangItem);
        sendToAllExcept(curPlayer, new ProtocolItem(Protocol.RIVAL_PENG_OR_GANG, new Object[]{gangItem, curPlayer.order, gameService.outCardPlayerOrder}));
        if (gangItem.type != GangType.PENG) distributeCardToNext(curPlayer.order - 1);
    }

    /**
     * 玩家选择pass按钮，不 胡碰杠自摸
     *
     * @param cardAndOrder 胡的牌和出牌玩家的次序
     */
    private void pass(Object[] cardAndOrder) {
        Integer rivalOrder = ((Integer) cardAndOrder[1]);
        Card rivalCard = JSON.parseObject(cardAndOrder[0].toString(), Card.class);
        if (gameService.canHuNum.get() == -1 || gameService.canHuNum.get() == SELF_HU) {
            // 玩家选择不碰或者杠时
            distributeCardToNext(rivalOrder);
            gameService.canHuNum.set(-1);
            gameService.nextPlayerOrder = -1;
        } else {
            // 有人胡
            int n = gameService.canHuNum.decrementAndGet();
            if (n == 0) {
                if (rivalOrder.equals(gameService.nextPlayerOrder)) {
                    // 没有人选择胡，查看是否有人要杠 碰
                    for (GamePlayer player : gameService.gameData.players) {
                        if (player.order.equals(rivalOrder)) continue;
                        GangItem gangItem = player.canGangOrPeng(rivalCard);
                        if (gangItem != null) {
                            sendTo(player, new ProtocolItem(Protocol.PENG_OR_GANG, new Object[]{gangItem, rivalOrder}));
                            sendDangerCards(player);
                            gameService.canHuNum.set(-1);
                            gameService.nextPlayerOrder = -1;
                            return;
                        }
                    }
                }
                // 有人选择胡，从gameService.nextPlayerOrder开始发牌
                distributeCardToNext(gameService.nextPlayerOrder);
                gameService.canHuNum.set(-1);
                gameService.nextPlayerOrder = -1;
            }
        }
    }

    /**
     * 玩家选择胡
     *
     * @param cardAndOrder 胡的牌和出牌玩家的次序
     */
    private void hu(Object[] cardAndOrder) {
        Integer rivalOrder = ((Integer) cardAndOrder[1]);
        Card rivalCard = JSON.parseObject(cardAndOrder[0].toString(), Card.class);
        gameService.huSelf(curPlayer, rivalCard);
        // 通知其他玩家，有人胡牌了
        sendToAllExcept(curPlayer, new ProtocolItem(Protocol.PLAYER_HU, new Object[]{rivalCard, curPlayer.order, gameService.outCardPlayerOrder}));
        // 如果是自摸，更新算法层数据
        if (curPlayer.order.equals(rivalOrder)) {
            gameService.outCardRival(curPlayer, rivalCard, 1);
        }

        int n = gameService.canHuNum.decrementAndGet();
        // 当有多个玩家时，同步计算下一个发牌位置
        synchronized (gameService) {
            if (gameService.nextPlayerOrder == -1) gameService.nextPlayerOrder = curPlayer.order;
            else {
                int selfOrder = curPlayer.order - rivalOrder;
                selfOrder = selfOrder >= 0 ? selfOrder : selfOrder + gameService.gameData.players.size();
                int otherOrder = gameService.nextPlayerOrder - rivalOrder;
                otherOrder = otherOrder >= 0 ? otherOrder : otherOrder + gameService.gameData.players.size();
                if (selfOrder > otherOrder) gameService.nextPlayerOrder = curPlayer.order;
            }
        }
        // 要胡的玩家都已经确认是否要胡了
        if (n == 0 || n == SELF_HU - 1) {
            distributeCardToNext(gameService.nextPlayerOrder);
            gameService.nextPlayerOrder = -1;
            gameService.canHuNum.set(-1);
        }
    }

    /**
     * 处理客户端发送来的缺牌类型
     *
     * @param type 类型
     */
    private void receiveLackType(Type type) {
//        log.info("sessionId: {}, name: {}, 收到玩家定缺类型：{}", session.getId(), player.name, type);
        gameService.defineLack(curPlayer, type);
        boolean allReady = true;
        for (GamePlayer player : gameService.gameData.players) {
            allReady = allReady && player.lack != null;
        }

        // 如果所有玩家都已经定缺，就发送所有玩家的定缺数据到所有玩家中
        if (!allReady) return;
        String[] lackTypes = new String[gameService.gameData.players.size()];
        for (int i = 0; i < gameService.gameData.players.size(); i++) {
            lackTypes[i] = gameService.gameData.players.get(i).lack.name();
        }

        sendToAll(new ProtocolItem(Protocol.SEND_ALL_LACK_TYPE, lackTypes));

        for (GamePlayer player : gameService.gameData.players) {
            sendDangerCards(player);
        }
    }

    /**
     * 玩家自己打出来一张牌
     *
     * @param card 打的牌
     */
    private void playerOutCard(Card card) {
        log.info("session id: {}, name: {}, 出牌: {}", session.getId(), curPlayer.name, card);
        // 更新算法数据
        gameService.outCardSelf(curPlayer, card);
        // 发送card到其他玩家
        sendToAllExcept(curPlayer, new ProtocolItem(Protocol.RIVAL_OUT_CARD, new Object[]{curPlayer.order, card}));
        // 检查是否有玩家要胡、碰、杠
        List<GamePlayer> huPlayers = gameService.gameData.players.stream()
                .filter(player -> !player.equals(curPlayer) && player.canHu(card)).collect(Collectors.toList());

        GangItem gangItem = null;
        GamePlayer gangPlayer = null;
        for (GamePlayer player : gameService.gameData.players) {
            if (curPlayer.equals(player) || player.alreadyHu) continue;
            gangItem = player.canGangOrPeng(card);
            gangPlayer = player;
            if (gangItem != null) break;
        }

        if (huPlayers.size() > 0) {
            // 发送消息提醒玩家胡牌
            huMethodService(card, huPlayers, Protocol.IF_HU, curPlayer.order);
            gameService.outCardPlayerOrder = curPlayer.order;
        } else if (gangItem != null) {
            // 发送消息提醒玩家杠或者碰牌
            sendTo(gangPlayer, new ProtocolItem(Protocol.PENG_OR_GANG, new Object[]{gangItem, curPlayer.order}));
            gameService.outCardPlayerOrder = curPlayer.order;
            // 玩家要碰 杠牌时也会出牌，重新生成危险牌提示玩家
            sendDangerCards(gangPlayer);
        } else {
            // 给下一个玩家（还没胡）发牌
            distributeCardToNext(curPlayer.order);
        }
    }

    /**
     * 当前玩家打出来一张牌时，有玩家要胡，发送消息通知要胡的玩家
     *
     * @param card      当前玩家打出来的牌
     * @param huPlayers 要胡的玩家
     * @param huType    胡的类型（自摸、别人放炮）
     */
    private void huMethodService(Card card, List<GamePlayer> huPlayers, Protocol huType, int order) {
        gameService.canHuNum.set(huPlayers.size());
        if (huPlayers.size() == 1 && huPlayers.get(0).order == order)
            gameService.canHuNum.set(SELF_HU);

        gameService.nextPlayerOrder = order;
        for (GamePlayer huPlayer : huPlayers) {
            sendTo(huPlayer, new ProtocolItem(huType, new Object[]{card, order}));
        }
    }

    /**
     * 给下一个玩家发牌
     *
     * @param startOrder 开始的位置，实际上是从starOrder的下一个开始
     */
    private void distributeCardToNext(int startOrder) {
        for (int i = 0; i < gameService.gameData.players.size() - 1; i++) {
            int index = (i + startOrder + 1) % gameService.gameData.players.size();
            GamePlayer player = gameService.gameData.players.get(index);
            if (player.alreadyHu) continue;
            distributeCard(player);
            break;
        }
    }

    /**
     * 客户端请求开始游戏，处理相关逻辑
     * 1 初始化gameService
     * 2 发送手牌给玩家
     * 3 给第一个玩家发一张牌
     * 4 定缺
     */
    private void beginGame() {
        gameService.initGameService();
        gameService.alreadyStart = true;

        for (GamePlayer gamePlayer : gameService.gameData.players) {
            List<Card> cards = new ArrayList<>(13);
            for (Map.Entry<Card, Integer> cardEntry : gamePlayer.cardMap.entrySet()) {
                for (int i = 0; i < cardEntry.getValue(); i++) {
                    Card card = cardEntry.getKey();
                    cards.add(new Card(card.num, card.type));
                }
            }

            // 发送手牌
            Session curSession = sessionMap.get(gamePlayer.sessionId);
            sendMsg(curSession, new ProtocolItem(Protocol.BEGIN_GAME_SEND_SELF_CARDS, cards));
//            // 发送危险牌数组
//            sendDangerCards(gamePlayer);
        }

        // 先给第一个玩家发一张牌
        distributeCard(gameService.gameData.players.get(0));

        // 定缺
        sendToAll(new ProtocolItem(Protocol.DEFINE_LACK));
    }

    /**
     * 给指定玩家发牌
     *
     * @param player 玩家
     */
    private void distributeCard(GamePlayer player) {
        // 检查是否游戏结束
        if (gameService.gameOver()) {
            sendToAll(new ProtocolItem(Protocol.GAME_OVER));
            return;
        }

        // 给玩家发一张牌
        Card card = gameService.distributeCard(player);
        sendTo(player, new ProtocolItem(Protocol.DISTRIBUTE_ONE_CARD, card));
        // 通知其他玩家发了一张牌
        sendToAllExcept(player, new ProtocolItem(Protocol.RIVAL_GIVE_CARD, player.order));
        // 给 player 发送危险牌数据
        sendDangerCards(player);
        // 检测玩家是否时自摸
        if (player.canHu(card)) {
            huMethodService(card, new ArrayList<GamePlayer>() {{
                add(player);
            }}, Protocol.IF_SELF_HU, player.order);
        }
        // 检测玩家是否要暗杠 加杠
        List<GangItem> gangItems = player.canSelfGang();
        if (gangItems.size() > 0) {
            sendTo(player, new ProtocolItem(Protocol.IF_SELF_GANG, gangItems));
        }
    }

    /**
     * 发送危险牌数组
     *
     * @param gamePlayer 要发送的玩家
     */
    private void sendDangerCards(GamePlayer gamePlayer) {
        List<String> cardList = gameService.calRivalDanger(gamePlayer).stream().map(
                card -> card.type.name() + card.num).collect(Collectors.toList());
        sendTo(gamePlayer, new ProtocolItem(Protocol.SEND_DANGER_CARDS, cardList));
    }

    /**
     * 注解 @OnError 表示方法是出现错误时的回调方法
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.info("发生错误！sessionID = " + session.getId() + ", name = " + curPlayer.name);
        error.printStackTrace();
    }

    /**
     * 发送给所有玩家
     *
     * @param data 数据
     */
    public void sendToAll(Object data) {
        String msg = JSON.toJSONString(data);
        for (GamePlayer gamePlayer : gameService.gameData.players) {
            sendMsg(sessionMap.get(gamePlayer.sessionId), msg);
        }
    }

    /**
     * 发送给所有玩家，除开player
     *
     * @param data 数据
     */
    public void sendToAllExcept(GamePlayer player, Object data) {
        String msg = JSON.toJSONString(data);
        for (GamePlayer gamePlayer : gameService.gameData.players) {
            if (gamePlayer.equals(player)) continue;
            sendMsg(sessionMap.get(gamePlayer.sessionId), msg);
        }
    }

    /**
     * 发送给指定玩家
     *
     * @param data 数据
     */
    public void sendTo(GamePlayer player, Object data) {
        sendMsg(sessionMap.get(player.sessionId), data);
    }

    /**
     * 使用指定session，将obj转换成JSON格式发送
     */
    public void sendMsg(Session session, String str) {
        logSendInfo(str);
        try {
            session.getBasicRemote().sendText(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 使用指定session，将obj转换成JSON格式发送
     */
    public void sendMsg(Session session, Object obj) {
        logSendInfo(obj);
        try {
            session.getBasicRemote().sendText(JSON.toJSONString(obj));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void logSendInfo(String str) {
        log.info("发送消息:{}", str);
    }

    private void logSendInfo(Object obj) {
        log.info("发送消息:{}", JSON.toJSONString(obj));
    }
}
